#include <stdlib.h>
#include <assert.h>
#include "action.h"
#include "player.h"
#include "main.h"
#include "items.h"
#include "bailout.h"

extern Player player [MAX_PLAYERS]; // as declared in network.h

const Action no_action = { ACTION_NONE, -1, 0, 0, {0,0,0}, DIR_NONE, {NULL} };

coord_t location_from_actor (int actor)
{
	Player* p = (Player*) player_from_actor_id (actor);
	coord_t c = (coord_t) { p->pos.x, p->pos.y, p->pos.z };
	if (p->action.type == ACTION_MOVE) 
		return direction_apply (p->action.location, p->action.dir, 1);
	else
		return c;
}

coord_t respawn_location (int actor)
{
	if (actor_type (actor) != ACTOR_PLAYER) 
		bailout ("sds", "Cannot respawn actor ", actor, ", which is not a player.");
		
	Player* p = (Player*) player_from_actor_id (actor);
	return p->checkpoint;
}

Direction dir_from_actor (int actor)
{
	Player* p = (Player*) player_from_actor_id (actor);
	if (p->action.type == ACTION_NONE) return p->pos.dir;
	else return p->action.dir;
}

Action action_alloc_turn (int actor, int start_time, Direction dir)
{
	Action action = { 
		ACTION_TURN,                 // type
		actor,                       // actor
		start_time,                  // start_time
		COOLDOWN_TURN,               // cooldown
		location_from_actor (actor), // location
		dir,                         // dir
		{NULL}                       // detail
	};
	return action;
}

Action action_alloc_move (int actor, int start_time, Direction dir)
{
	Action action = { 
		ACTION_MOVE,                 // type
		actor,                       // actor
		start_time,                  // start_time
		COOLDOWN_MOVE,               // cooldown
		location_from_actor (actor), // location
		dir,                         // dir
		{NULL}                       // detail
	};
	return action;
}

Action action_alloc_halt (int actor, int start_time)
{
	Action action = { 
		ACTION_HALT, 
		actor, 
		start_time, 
		COOLDOWN_HALT,
		location_from_actor (actor), // location
		dir_from_actor (actor),      // dir
		{NULL}                       // detail
	};
	return action;
}

Action action_alloc_closeattack (int actor, int start_time, int weapon)
{
	assert ((weapon >= WEAPONS_CLOSE_BEGIN) && (weapon < WEAPONS_CLOSE_END));
	
	Action action = { 
		ACTION_ATTACK_CLOSE, 
		actor, 
		start_time, 
		COOLDOWN_ATTACK,
		location_from_actor (actor), // location
		dir_from_actor (actor),      // dir
		{NULL}                       // detail
	};
	action.cattack = malloc (sizeof(CloseAttackAction));
	action.cattack->weapon = weapon;
	return action;
}

Action action_alloc_rangeattack (int actor, int start_time, int weapon)
{
	assert ((weapon >= WEAPONS_RANGE_BEGIN) && (weapon < WEAPONS_RANGE_END));
	
	Action action = { 
		ACTION_ATTACK_RANGE, 
		actor, 
		start_time, 
		COOLDOWN_ATTACK,
		location_from_actor (actor), // location
		dir_from_actor (actor),      // dir
		{NULL}                       // detail
	};
	action.rattack = malloc (sizeof(RangeAttackAction));
	action.rattack->weapon = weapon;
	return action;
}

Action action_alloc_traverse (int actor, int start_time, coord_t target)
{
	Action action = { 
		ACTION_TRAVERSE, 
		actor, 
		start_time, 
		COOLDOWN_TRAVERSE,
		location_from_actor (actor), // location
		dir_from_actor (actor),      // dir
		{NULL}                       // detail
	};
	action.traverse = malloc (sizeof(TraverseAction));
	action.traverse->target = target;
	return action;
}

Action action_alloc_die (int actor, int start_time)
{
	Action action = { 
		ACTION_DIE, 
		actor, 
		start_time, 
		COOLDOWN_DIE,
		location_from_actor (actor), // location
		dir_from_actor (actor),      // dir
		{NULL}                       // detail
	};
	return action;
}

extern Action action_alloc_respawn (int actor, int start_time)
{
	Action action = { 
		ACTION_RESPAWN, 
		actor, 
		start_time, 
		COOLDOWN_RESPAWN,
		respawn_location (actor), // location
		DIR_DOWN,                 // dir
		{NULL}                    // detail
	};
	return action;
}

void action_free (Action* a)
{
	switch (a->type) {
	case ACTION_ATTACK_CLOSE: free (a->cattack);  a->cattack  = NULL; break;
	case ACTION_ATTACK_RANGE: free (a->rattack);  a->rattack  = NULL; break;
	case ACTION_TRAVERSE:     free (a->traverse); a->traverse = NULL; break;
	case ACTION_TURN:
	case ACTION_MOVE:
	case ACTION_HALT:
	case ACTION_DIE:
	case ACTION_RESPAWN:
	case ACTION_NONE: break; // nothing to do
	default: assert (0);
	}
	a->cattack = NULL; 
}

int action_is_equal (const Action* a1, const Action* a2)
{
	if (a1->type != a2->type) return 0;
	if (a1->actor != a2->actor) return 0;
	if (a1->start_time != a2->start_time) return 0;
	if (a1->location.x != a2->location.x) return 0;
	if (a1->location.y != a2->location.y) return 0;
	if (a1->location.z != a2->location.z) return 0;
	if (a1->dir != a2->dir) return 0;
	
	return 1;
}

int actor_id_from_player (struct Player* player_obj)
{
	int id = (Player*)player_obj - &player[0];
	assert ((id >= 0) && (id < MAX_PLAYERS));
	return id;
}

ActorType actor_type (int id)
{
	if ((id >= 0) && (id < MAX_PLAYERS)) return ACTOR_PLAYER;
	// ACTOR_MONSTER: reserved space here
	else return ACTOR_INVALID;
}

struct Player* player_from_actor_id (int id)
{
	assert ((id >= 0) && (id < MAX_PLAYERS));
	return (struct Player*) &player[id];
}
